package ru.papergames.battleserver;

import org.apache.log4j.Logger;
import org.mmocore.network.ReceivablePacket;
import ru.papergames.Config;
import ru.papergames.battleserver.network.PaperGameClient;
import ru.papergames.battleserver.network.WebServerClient;
import ru.papergames.battleserver.util.StringUtil;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class BattleThreadPoolManager {
    protected static final Logger _log = Logger.getLogger(BattleThreadPoolManager.class.getName());

    private ThreadPoolExecutor playersPacketsThreadPool;
    private ThreadPoolExecutor ioPacketsThreadPool;
    private ThreadPoolExecutor wsPacketsThreadPool;
    // will be really used in the next AI implementation.
    //private ThreadPoolExecutor _aiThreadPool;
    private ThreadPoolExecutor generalThreadPool;
    private ThreadPoolExecutor disconnectTasksThreadPool;

    private ThreadPoolExecutor npcsTasksThreadPool;

    /**
     * temp workaround for VM issue
     */
    private static final long MAX_DELAY = Long.MAX_VALUE / 1000000 / 2;

    private boolean _shutdown;

    public static BattleThreadPoolManager getInstance() {
        return SingletonHolder._instance;
    }

    private BattleThreadPoolManager() {
        ioPacketsThreadPool = new ThreadPoolExecutor(Config.IO_PACKET_THREAD_CORE_SIZE, Config.IO_PACKET_THREAD_CORE_SIZE + 4, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory("I/O Packet Pool", Thread.NORM_PRIORITY + 1));
        wsPacketsThreadPool = new ThreadPoolExecutor(Config.WS_PACKET_THREAD_CORE_SIZE, Config.WS_PACKET_THREAD_CORE_SIZE + 4, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory("I/O Packet Pool", Thread.NORM_PRIORITY + 1));
        playersPacketsThreadPool = new ThreadPoolExecutor(Config.GENERAL_PACKET_THREAD_CORE_SIZE, Config.GENERAL_PACKET_THREAD_CORE_SIZE + 10, 15L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory("Normal Packet Pool", Thread.NORM_PRIORITY + 1));

        generalThreadPool = new ThreadPoolExecutor(Config.GENERAL_THREAD_CORE_SIZE, Config.GENERAL_THREAD_CORE_SIZE + 2, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory("General Pool", Thread.NORM_PRIORITY));
        disconnectTasksThreadPool = new ThreadPoolExecutor(2, 4, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory("disc Pool", Thread.NORM_PRIORITY));
        
        npcsTasksThreadPool = new ThreadPoolExecutor(2, 3, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory("Npcs Pool", Thread.NORM_PRIORITY));
    }

    public static long validateDelay(long delay) {
        if (delay < 0) {
            delay = 0;
        } else if (delay > MAX_DELAY) {
            delay = MAX_DELAY;
        }
        return delay;
    }

    public void executePacket(ReceivablePacket<PaperGameClient> pkt) {
        playersPacketsThreadPool.execute(pkt);
    }

    public void executeWSPacket(ReceivablePacket<WebServerClient> rp) {
        wsPacketsThreadPool.execute(rp);
    }

    public void executeTask(Runnable r) {
        generalThreadPool.execute(r);
    }

    public void executeDisconnectTask(Runnable disconnectTask) {
        disconnectTasksThreadPool.execute(disconnectTask);
    }

    public void executeNpcTask(Runnable disconnectTask) {
        npcsTasksThreadPool.execute(disconnectTask);
    }

    public String[] getStats() {
        return new String[]{
//                " + AI:",
//                " |- ActiveThreads:   " + _aiScheduledThreadPool.getActiveCount(),
//                " |- getCorePoolSize: " + _aiScheduledThreadPool.getCorePoolSize(),
//                " |- PoolSize:        " + _aiScheduledThreadPool.getPoolSize(),
//                " |- MaximumPoolSize: " + _aiScheduledThreadPool.getMaximumPoolSize(),
//                " |- CompletedTasks:  " + _aiScheduledThreadPool.getCompletedTaskCount(),
//                " |- ScheduledTasks:  " + (_aiScheduledThreadPool.getTaskCount() - _aiScheduledThreadPool.getCompletedTaskCount()),
                "TP:",
                " + Packets:",
                " |- ActiveThreads:   " + playersPacketsThreadPool.getActiveCount(),
                " |- getCorePoolSize: " + playersPacketsThreadPool.getCorePoolSize(),
                " |- MaximumPoolSize: " + playersPacketsThreadPool.getMaximumPoolSize(),
                " |- LargestPoolSize: " + playersPacketsThreadPool.getLargestPoolSize(),
                " |- PoolSize:        " + playersPacketsThreadPool.getPoolSize(),
                " |- CompletedTasks:  " + playersPacketsThreadPool.getCompletedTaskCount(),
                " |- QueuedTasks:     " + playersPacketsThreadPool.getQueue().size(),
                " | -------",
                " + I/O Packets:",
                " |- ActiveThreads:   " + ioPacketsThreadPool.getActiveCount(),
                " |- getCorePoolSize: " + ioPacketsThreadPool.getCorePoolSize(),
                " |- MaximumPoolSize: " + ioPacketsThreadPool.getMaximumPoolSize(),
                " |- LargestPoolSize: " + ioPacketsThreadPool.getLargestPoolSize(),
                " |- PoolSize:        " + ioPacketsThreadPool.getPoolSize(),
                " |- CompletedTasks:  " + ioPacketsThreadPool.getCompletedTaskCount(),
                " |- QueuedTasks:     " + ioPacketsThreadPool.getQueue().size(),
                " | -------",
                " + WS Packets:",
                " |- ActiveThreads:   " + wsPacketsThreadPool.getActiveCount(),
                " |- getCorePoolSize: " + wsPacketsThreadPool.getCorePoolSize(),
                " |- MaximumPoolSize: " + wsPacketsThreadPool.getMaximumPoolSize(),
                " |- LargestPoolSize: " + wsPacketsThreadPool.getLargestPoolSize(),
                " |- PoolSize:        " + wsPacketsThreadPool.getPoolSize(),
                " |- CompletedTasks:  " + wsPacketsThreadPool.getCompletedTaskCount(),
                " |- QueuedTasks:     " + wsPacketsThreadPool.getQueue().size(),
                " | -------",
                " + General Tasks:",
                " |- ActiveThreads:   " + generalThreadPool.getActiveCount(),
                " |- getCorePoolSize: " + generalThreadPool.getCorePoolSize(),
                " |- MaximumPoolSize: " + generalThreadPool.getMaximumPoolSize(),
                " |- LargestPoolSize: " + generalThreadPool.getLargestPoolSize(),
                " |- PoolSize:        " + generalThreadPool.getPoolSize(),
                " |- CompletedTasks:  " + generalThreadPool.getCompletedTaskCount(),
                " |- QueuedTasks:     " + generalThreadPool.getQueue().size(),
                " | -------",
                " + AI:",
                " |- Not Done"
        };
    }

    private class PriorityThreadFactory implements ThreadFactory {
        private int _prio;
        private String _name;
        private AtomicInteger _threadNumber = new AtomicInteger(1);
        private ThreadGroup _group;

        public PriorityThreadFactory(String name, int prio) {
            _prio = prio;
            _name = name;
            _group = new ThreadGroup(_name);
        }

        /* (non-Javadoc)
           * @see java.util.concurrent.ThreadFactory#newThread(java.lang.Runnable)
           */

        public Thread newThread(Runnable r) {
            Thread t = new Thread(_group, r);
            t.setName(_name + "-" + _threadNumber.getAndIncrement());
            t.setPriority(_prio);
            return t;
        }

        public ThreadGroup getGroup() {
            return _group;
        }
    }

    /**
     *
     */
    public void shutdown() {
        _shutdown = true;
        try {
            playersPacketsThreadPool.awaitTermination(1, TimeUnit.SECONDS);
            ioPacketsThreadPool.awaitTermination(1, TimeUnit.SECONDS);
            wsPacketsThreadPool.awaitTermination(1, TimeUnit.SECONDS);
            generalThreadPool.awaitTermination(1, TimeUnit.SECONDS);
//            _aiThreadPool.awaitTermination(1, TimeUnit.SECONDS);
            playersPacketsThreadPool.shutdown();
            ioPacketsThreadPool.shutdown();
            wsPacketsThreadPool.shutdown();
            generalThreadPool.shutdown();
//            _aiThreadPool.shutdown();
            _log.info("All ThreadPools are now stoped");

        }
        catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public boolean isShutdown() {
        return _shutdown;
    }

    /**
     *
     */
    public void purge() {
        ioPacketsThreadPool.purge();
        wsPacketsThreadPool.purge();
        playersPacketsThreadPool.purge();
        generalThreadPool.purge();
    }

    /**
     *
     */
    public String getPacketStats() {
        final StringBuilder sb = new StringBuilder(1000);
        ThreadFactory tf = playersPacketsThreadPool.getThreadFactory();
        if (tf instanceof PriorityThreadFactory) {
            PriorityThreadFactory ptf = (PriorityThreadFactory) tf;
            int count = ptf.getGroup().activeCount();
            Thread[] threads = new Thread[count + 2];
            ptf.getGroup().enumerate(threads);
            StringUtil.append(sb, "General Packet Thread Pool:\r\n" + "Tasks in the queue: ", String.valueOf(playersPacketsThreadPool.getQueue().size()), "\r\n"
                    + "Showing threads stack trace:\r\n" + "There should be ", String.valueOf(count), " Threads\r\n");
            for (Thread t : threads) {
                if (t == null)
                    continue;

                StringUtil.append(sb, t.getName(), "\r\n");
                for (StackTraceElement ste : t.getStackTrace()) {
                    StringUtil.append(sb, ste.toString(), "\r\n");
                }
            }
        }

        sb.append("Packet Tp stack traces printed.\r\n");

        return sb.toString();
    }

    public String getIOPacketStats() {
        final StringBuilder sb = new StringBuilder(1000);
        ThreadFactory tf = ioPacketsThreadPool.getThreadFactory();

        if (tf instanceof PriorityThreadFactory) {
            PriorityThreadFactory ptf = (PriorityThreadFactory) tf;
            int count = ptf.getGroup().activeCount();
            Thread[] threads = new Thread[count + 2];
            ptf.getGroup().enumerate(threads);
            StringUtil.append(sb, "I/O Packet Thread Pool:\r\n" + "Tasks in the queue: ", String.valueOf(ioPacketsThreadPool.getQueue().size()), "\r\n"
                    + "Showing threads stack trace:\r\n" + "There should be ", String.valueOf(count), " Threads\r\n");

            for (Thread t : threads) {
                if (t == null)
                    continue;

                StringUtil.append(sb, t.getName(), "\r\n");

                for (StackTraceElement ste : t.getStackTrace()) {
                    StringUtil.append(sb, ste.toString(), "\r\n");
                }
            }
        }

        sb.append("Packet Tp stack traces printed.\r\n");

        return sb.toString();
    }

    public String getGeneralStats() {
        final StringBuilder sb = new StringBuilder(1000);
        ThreadFactory tf = generalThreadPool.getThreadFactory();

        if (tf instanceof PriorityThreadFactory) {
            PriorityThreadFactory ptf = (PriorityThreadFactory) tf;
            int count = ptf.getGroup().activeCount();
            Thread[] threads = new Thread[count + 2];
            ptf.getGroup().enumerate(threads);
            StringUtil.append(sb, "General Thread Pool:\r\n" + "Tasks in the queue: ", String.valueOf(generalThreadPool.getQueue().size()), "\r\n"
                    + "Showing threads stack trace:\r\n" + "There should be ", String.valueOf(count), " Threads\r\n");

            for (Thread t : threads) {
                if (t == null)
                    continue;

                StringUtil.append(sb, t.getName(), "\r\n");

                for (StackTraceElement ste : t.getStackTrace()) {
                    StringUtil.append(sb, ste.toString(), "\r\n");
                }
            }
        }

        sb.append("Packet Tp stack traces printed.\r\n");

        return sb.toString();
    }

    @SuppressWarnings("synthetic-access")
    private static class SingletonHolder {
        protected static final BattleThreadPoolManager _instance = new BattleThreadPoolManager();
	}
}